/*
 * This file is part of SSHTTPD.
 *
 * SSHTTPD is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * SSHTTPD is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/** @file
 * Main application file, Code ranges from parsing command line args to 
 * spawning children that listen to web requests.
 */

/* Application level includes. */
#include "include/main.h"
#include "include/utils.h"
#include "include/socket.h"
#include "include/request.h"
#include "include/plugin.h"
#include "include/response.h"

/* System level includes. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>

/**
 * Accepts a client and handles the request.
 * @param sd A BSD socket descriptor.
 */
static void handlereq(int sd) {
	int ud;
	socklen_t caddrsize;
	struct sockaddr_in caddr;
	char *requeststr;
	httpr_t *requestp;
	httpresp_t *resp;

	/* Accept a new connection. */
	caddrsize = sizeof(caddr);
	ud = accept(sd, (struct sockaddr*)&caddr, &caddrsize);
	event(ELOG, "http connection from %s", inet_ntoa(caddr.sin_addr));
	
	if(!(requeststr = readrequest(ud))) {
		close(ud);
		return;
	}

	/*(*callbacks[0])("test"); why crash :/ */

	/* Check these returns too eh. */
	requestp = reqparse(requeststr);
	resp = build_response(requestp);
	
	send_response(resp, ud);

	/* Clean up the shit. */
	destroyrequest(requestp);
	destroy_response(resp);

	free(requeststr);
	close(ud);

	event(ELOG, "http connection closed");
}

/** INFINITY child life value. */
#define INF 0xBABE 
/** Child life is Request based. */
#define CLIFE_REQ 0 
/** Child life is Time based. (not implemented) */
#define CLIFE_TIME 1 

/**
 * Spawn a child proccess and start it listening for requests.
 * @param sd The socket descriptor to listen for connections on.
 * @param ctype Type of child to make, currently only CLIFE_REQ is implmented.
 * @param life The amount of requests the child will live for, INF for forever.
 * @see INF, CLIFE_REQ, CLIFE_TIME
 */
static void childproc(int sd, int ctype, int life) { 
	int pid, count;
	
	count = 0;
	
	/* Everything ran from this point as pid 0 is a child, think about this 
	 * when you handle errors and exit(). */
	pid = fork();
	
	/* Split based on if this is the child or parent, 0 for child. */
	if(pid == 0) {
		/* Switch on the childs life type. */
		switch(ctype) {
			case CLIFE_REQ:
				event(ELOG, "child proc forked with a %d run's lifespan", life);
				while(1) {
					/* If we are not immortal and our life is up die. */
					if((life != INF) && (count >= life)) {
						event(ELOG, "child reached max of %d runs old)", count);
						break;
					}
					
					/* Run the client collection/handleing routine. */
					handlereq(sd);
					count++;
				}
				exit(0);
				
			case CLIFE_TIME:
				event(EWARN, "CLIFE_TIME not implemented");
				exit(1);
				
			default:
				event(EPROB, "unknown child CLIFE");
				exit(1);
				
		}
	}
}

/** Set to the socket descriptor thats listening for connections. */
int gsd; 

/**
 * Handles signal interrupts sent by the operating systen.
 * @param sig Signal recieved.
 */
static void signalhandler(int sig) {
	int status, save_errno;
	event(ELOG, "signal recieved");
	
	/* What signals do we want to act on?. */
	switch(sig) {
			/* Reaper. */
		case SIGCHLD:
			/* Called by an interrupt when a child exits, 
			 * we must wait() so it doesnt become a zombie. */
			save_errno = errno;
			while(wait3(&status, WNOHANG, NULL) > 0);	
			errno = save_errno;
			event(ELOG, "child died, exit code %d", status); /* HRM: not always a problem is it? */
			if(status == 11) 
				event(ELOG, "this is a known bug, dont try to read / yet :)");
			
			break;
			
			/* Some experimental signals to spawn new clients by external request,
			 * SIGUSR1 for request based children and SIGUSR2 for TIME based. */
		case SIGUSR1:
			childproc(gsd, CLIFE_REQ, 5);
			break;
		case SIGUSR2:
			childproc(gsd, CLIFE_TIME, 5);
			break;
	}
}

#ifndef UTEST
int main(int argc, char **argv) {
	struct sockaddr_in addr;
	int s, c, i, port, children;
	port = 80;
	children = 5;
	
	/* Parse the command line args; */
	while((c = getopt(argc, argv, "p:c:h")) != -1) {
		switch(c) {
			/* p for port. */
			case 'p':
				port = atoi(optarg);
				break;
			/* c for (initial, immortal) children. */
			case 'c':
				children = atoi(optarg);
				break;
			case 'h':
				printf("sshttpd -p <port num>(default:80) -c <immortal children>(default:5) -h\n"); 
				return 0;
			/* An argument was given without its mandatory value if it has one. */
			case '?':
				return 1;
		}
	}
	
	event(ECON, VERSION);
	event(ELOG, "parent pid is %d", getpid());
	event(ELOG, "listening on port: %d", port);
	
	/* If a socket is disconnected while a child is still writeing.
	 * SIGPIPE will interupt and kill the child, so we ignore it. */
	signal(SIGPIPE, SIG_IGN);
	
	/* Init the socket subsystem and make some children. */
	if((s = init_socket(&addr, sizeof addr, port)) == -1)
		return 1;

	gsd = s;
	
	/* Experimental module system. */
	/*cmod("/home/daniel/work/sshttpd/modules/mod.o"); */

	for(i = 0; i < children; i++)
		childproc(s, CLIFE_REQ, INF);
	
	/* These are done after the child are spawned because we only want the parent proc to catch these signals. 
	 * Set a reaper function to kill zombie children */
	signal(SIGCHLD, signalhandler); 
	/* USR signals to allow sysadmins to spawn children. */
	signal(SIGUSR1, signalhandler);
	signal(SIGUSR2, signalhandler);


	/* Busy loop so the main proc doesnt exit once done. 
	 * probably going to do some dynamic forking. */
	while(1) {
		sleep(INF);
	}
	
	close(s);
	return 0;
}
#endif
